package middleware

import (
	"bigdevops/src/common"
	"bigdevops/src/config"
	"bigdevops/src/models"
	"encoding/base64"
	"fmt"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"strings"
	"time"
)

func JWTAuthMiddleware() func(c *gin.Context) {
	return func(c *gin.Context) {

		// 头部判断服务账号的逻辑
		saHeaderString := c.Request.Header.Get(common.JWT_SA_HEADER)
		if saHeaderString != "" {
			// 	saHeaderString的 固定格式： username@@@token base64的编码
			resBytes, _ := base64.StdEncoding.DecodeString(saHeaderString)
			res := strings.Split(string(resBytes), "@@@")
			if len(res) == 2 {
				userName, saToken := res[0], res[1]
				// 去数据库中找到这个user
				saDbObj, err := models.GetUserByUserName(userName)
				fmt.Println(err)
				if saDbObj != nil {
					// 判断token是否和数据库中一致
					if saDbObj.ServiceAccountToken == saToken {
						c.Set(common.GIN_CTX_JWT_USER_NAME, userName)
						c.Next()
						return
					}
				}

			}

		}

		// 01 去header里面 找 Authorization 如果没有 直接报 401
		// 02 如果有 拿到token 长字符串 jwt 解析
		// 03 验证 token又没有过期
		// 04 将claim对象 传递下去，给业务处理函数使用
		// 05 续期

		// 01 去header里面 找 Authorization 如果没有 直接报 401
		authHeaderString := c.Request.Header.Get("Authorization")
		if authHeaderString == "" {
			common.Req401WithWithDetailed(gin.H{"reload": true}, "未登录或非法访问没有Authorization的header", c)
			c.Abort()
			return
		}

		// authHeaderString格式校验： Bearer wdkkwdk12312k3dj32ijed2j897fdhwuenf3n4iuryysuhsdjnbfdkjsad
		parts := strings.SplitN(authHeaderString, " ", 2)

		if !(len(parts) == 2 && parts[0] == "Bearer") {
			common.Req401WithWithDetailed(gin.H{"reload": true}, "请求头中的auth格式错误", c)
			//阻止调用后续的函数
			c.Abort()
			return
		}

		// 02 如果有 拿到token 长字符串 jwt 解析
		sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)
		// parseToken 解析token包含的信息
		userClaims, err := models.ParseToken(parts[1], sc)
		if err != nil {
			common.Req401WithWithDetailed(gin.H{"reload": true}, fmt.Sprintf("parseToken 解析token包含的信息错误:%v", err.Error()), c)
			//阻止调用后续的函数
			c.Abort()
			return
		}

		// 续期的逻辑 还没写
		// 判断token是否快过期 ，定义1个临期窗口
		// 重新生成一个token ，需要前端配合 下次请求的时候带上新的token

		// 假设 过期时间 10:00:00
		// 假设 当前时间 09:59:01
		// 假设 缓冲时间段 1分钟
		// 结果 59 < 60

		if userClaims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix() < int64(sc.JWTC.BufferDuration/time.Second) {

			sc.Logger.Info("jwt临期了,刷新jwt",
				zap.String("user", userClaims.Username),
				zap.Any("老token过期时间", userClaims.RegisteredClaims.ExpiresAt),
				zap.Any("临期窗口", sc.JWTC.BufferDuration),
			)
			// 新生成一个token
			newToken, err := models.GenJwtToken(userClaims.User, sc)
			if err != nil {

				common.Result5XX(0, gin.H{}, fmt.Sprintf("parseToken 解析token包含的信息错误:%v", err.Error()), c)
				//阻止调用后续的函数
				c.Abort()
			}

			// 设置到header中
			c.Header("new-token", newToken)

		} else {
			//sc.Logger.Info("jwt还没到,无需刷新jwt",
			//	zap.String("user", userClaims.Username),
			//	zap.Any("老token过期时间", userClaims.RegisteredClaims.ExpiresAt),
			//	zap.Any("临期窗口", sc.JWTC.BufferDuration),
			//)
		}

		// 设置cliam 后面的处理函数就能拿到, 也可以只设置claim
		// 这里不用塞入完整的claim 设置一个不变的 userId 或者 userName
		c.Set(common.GIN_CTX_JWT_USER_NAME, userClaims.Username)
		c.Next()

	}

}
